package com.forsir.android.mhfinance.storeObjects;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;

import com.forsir.android.mhfinance.R;
import com.forsir.android.mhfinance.helper.DbAdapter;

public class Repeating extends IStorable {
	private static final String TAG = "MHFinance";

	public static final int TYPE_NONE = 0;
	public static final int TYPE_DAILY = 1;
	public static final int TYPE_WEEKLY = 2;
	public static final int TYPE_MONTHLY = 3;
	public static final int TYPE_MONTHLY_DAY = 4;
	public static final int TYPE_QUARTER_YEARLY = 5;
	public static final int TYPE_YEARLY = 6;

	public static final String REPEATING_ID = "repeating_id";

	private Calendar mActualDate = Calendar.getInstance();
	private Calendar mEndDate = Calendar.getInstance();
	private int mOccurrences = 1;
	private int mType = TYPE_NONE;
	private boolean setOcurrences = false;

	public Repeating() {
		mEndDate.setTimeInMillis(0);
		mActualDate.setTimeInMillis(0);
	}

	public Repeating(int type) {
		this();
		mType = type;
	}

	public Repeating(Cursor cursor) {
		this();
		fillFromCursor(cursor);
	}

	public Calendar getBeginDate(DbAdapter dbHelper) {
		final Calendar date = Calendar.getInstance();
		// get payment of repeating
		final Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, new String[] { DbAdapter.PAYMENT_DATE }, DbAdapter.PAYMENT_REPEATING + "=" + getId(), null,
		        null, null, DbAdapter.PAYMENT_DATE, 1);

		if (!cursor.moveToFirst()) {
			return null;
		}
		date.setTimeInMillis(cursor.getLong(0));
		cursor.close();

		return date;
	}

	public Calendar getActualDate() {
		return mActualDate;
	}

	public long getActualDateLong() {
		return mActualDate.getTimeInMillis();
	}

	public void setActualDate(Calendar actualDate) {
		mActualDate = actualDate;
	}

	public void setActualDateLong(long date) {
		mActualDate.setTimeInMillis(date);
	}

	public Calendar getEndDate() {
		return mEndDate;
	}

	public long getEndDateLong() {
		return mEndDate.getTimeInMillis();
	}

	public String getEndDateString(Resources resource) {
		final SimpleDateFormat format = new SimpleDateFormat(resource.getString(R.string.format_date));
		return format.format(mEndDate.getTime());
	}

	public String getEndDateString() {
		final SimpleDateFormat format = new SimpleDateFormat("d.M.yyyy");
		return format.format(mEndDate.getTime());
	}

	public String getActualDateString() {
		final SimpleDateFormat format = new SimpleDateFormat("d.M.yyyy");
		return format.format(mActualDate.getTime());
	}

	public int getEndYear() {
		return mEndDate.get(Calendar.YEAR);
	}

	public int getEndMonth() {
		return mEndDate.get(Calendar.MONTH);
	}

	public int getEndDay() {
		return mEndDate.get(Calendar.DAY_OF_MONTH);
	}

	public void setEndDate(Calendar endDate) {
		mEndDate = endDate;
	}

	public void setEndDate(int year, int month, int day) {
		mEndDate.set(Calendar.YEAR, year);
		mEndDate.set(Calendar.MONTH, month);
		mEndDate.set(Calendar.DAY_OF_MONTH, day);
	}

	public void setEndDateLong(long date) {
		mEndDate.setTimeInMillis(date);
	}

	public int getOccurrences() {
		return mOccurrences;
	}

	public void addOcurences(int occurences, Calendar startDate) {
		setOccurrences(mOccurrences + occurences, startDate);
	}

	public void setOccurrences(int occurrences, Calendar startDate) {
		mOccurrences = occurrences;
		if (mOccurrences < 1) {
			mOccurrences = 1;
		}
		if (mOccurrences > 100) {
			mOccurrences = 100;
		}

		mEndDate = (Calendar) startDate.clone();

		switch (mType) {
			case Repeating.TYPE_NONE:
				break;

			case Repeating.TYPE_DAILY:
				mEndDate.add(Calendar.DATE, mOccurrences - 1);
				break;

			case Repeating.TYPE_WEEKLY:
				mEndDate.add(Calendar.WEEK_OF_YEAR, mOccurrences - 1);
				break;

			case Repeating.TYPE_MONTHLY:
				mEndDate.add(Calendar.MONTH, mOccurrences - 1);
				break;

			case Repeating.TYPE_MONTHLY_DAY:
				final int week = mEndDate.get(Calendar.DAY_OF_WEEK_IN_MONTH);
				final int day = mEndDate.get(Calendar.DAY_OF_WEEK);
				mEndDate.add(Calendar.MONTH, mOccurrences - 1);
				final Calendar firstDay = (Calendar) mEndDate.clone();
				firstDay.set(Calendar.DAY_OF_MONTH, 1);

				mEndDate.set(Calendar.DAY_OF_WEEK_IN_MONTH, week);
				mEndDate.set(Calendar.DAY_OF_WEEK, day);
				if (firstDay.get(Calendar.DAY_OF_WEEK) > mEndDate.get(Calendar.DAY_OF_WEEK)) {
					mEndDate.add(Calendar.WEEK_OF_MONTH, 1);
				}
				break;

			case Repeating.TYPE_QUARTER_YEARLY:
				mEndDate.add(Calendar.MONTH, 3 * (mOccurrences - 1));
				break;

			case Repeating.TYPE_YEARLY:
				mEndDate.add(Calendar.YEAR, mOccurrences - 1);
			default:
		}
	}

	public void computeOccurrences(DbAdapter dbHelper) {
		if (mType == TYPE_NONE) {
			return;
		}
		if (!setOcurrences) {
			final Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, new String[] { "count(*)" }, DbAdapter.PAYMENT_REPEATING + "=" + getId(), null, null,
			        null, null);
			if (!cursor.moveToFirst()) {
				mOccurrences = 1;
			}
			mOccurrences = cursor.getInt(0);
			cursor.close();
			setOcurrences = true;
		}
	}

	public void computeOccurrences(Calendar startDate) {
		switch (mType) {
			case Repeating.TYPE_NONE:
				break;

			case Repeating.TYPE_DAILY:
				if (mEndDate.get(Calendar.YEAR) == startDate.get(Calendar.YEAR)) {
					mOccurrences = mEndDate.get(Calendar.DAY_OF_YEAR) - startDate.get(Calendar.DAY_OF_YEAR) + 1;
				} else {
					final long diffDays = mEndDate.getTimeInMillis() - startDate.getTimeInMillis() / (24 * 60 * 60 * 1000);
					mOccurrences = (int) diffDays + 1;
				}
				break;

			case Repeating.TYPE_WEEKLY:
				mOccurrences = mEndDate.get(Calendar.YEAR) * 52 + mEndDate.get(Calendar.WEEK_OF_YEAR) - startDate.get(Calendar.YEAR) * 52
				        - startDate.get(Calendar.WEEK_OF_YEAR) + 1;
				break;

			case Repeating.TYPE_MONTHLY:
				mOccurrences = mEndDate.get(Calendar.YEAR) * 12 + mEndDate.get(Calendar.MONTH) - startDate.get(Calendar.YEAR) * 12 - startDate.get(Calendar.MONTH) + 1;
				break;

			case Repeating.TYPE_MONTHLY_DAY:
				mOccurrences = mEndDate.get(Calendar.YEAR) * 12 + mEndDate.get(Calendar.MONTH) - startDate.get(Calendar.YEAR) * 12 - startDate.get(Calendar.MONTH) + 1;
				final int week = mEndDate.get(Calendar.DAY_OF_WEEK_IN_MONTH) - startDate.get(Calendar.DAY_OF_WEEK_IN_MONTH);
				if (week == 0) {
					final int day = mEndDate.get(Calendar.DAY_OF_WEEK) - startDate.get(Calendar.DAY_OF_WEEK);
					if (day < 0) {
						mOccurrences--;
					}
				} else if (week < 0) {
					mOccurrences--;
				}
				break;

			case Repeating.TYPE_QUARTER_YEARLY:
				mOccurrences = mEndDate.get(Calendar.YEAR) * 12 + mEndDate.get(Calendar.MONTH) - startDate.get(Calendar.YEAR) * 12 - startDate.get(Calendar.MONTH);
				mOccurrences = (mOccurrences / 3) + 1;
				break;

			case Repeating.TYPE_YEARLY:
				mOccurrences = mEndDate.get(Calendar.YEAR) - startDate.get(Calendar.YEAR) + 1;
			default:
		}
	}

	public void setType(int type) {
		mType = type;
	}

	public void setType(int type, Calendar startDate) {
		setType(type);
		setOccurrences(mOccurrences, startDate);
	}

	public int getType() {
		return mType;
	}

	public CharSequence getTypeString(Resources resources) {
		CharSequence retString = "";

		switch (mType) {
			case Repeating.TYPE_NONE:
				retString = resources.getText(R.string.repeating_none);
				break;

			case Repeating.TYPE_DAILY:
				retString = resources.getText(R.string.repeating_daily);
				break;

			case Repeating.TYPE_WEEKLY:
				retString = resources.getText(R.string.repeating_weekly);
				break;

			case Repeating.TYPE_MONTHLY:
				retString = resources.getText(R.string.repeating_monthly);
				break;

			case Repeating.TYPE_MONTHLY_DAY:
				retString = resources.getText(R.string.repeating_monthly_day);
				break;

			case Repeating.TYPE_QUARTER_YEARLY:
				retString = resources.getText(R.string.repeating_quarter_yearly);
				break;

			case Repeating.TYPE_YEARLY:
				retString = resources.getText(R.string.repeating_yearly);
		}

		return retString;
	}

	public CharSequence getTypeShortString(Resources resources) {
		CharSequence retString = "";

		switch (mType) {
			case Repeating.TYPE_NONE:
				retString = resources.getText(R.string.repeating_abbrev_none);
				break;

			case Repeating.TYPE_DAILY:
				retString = resources.getText(R.string.repeating_abbrev_daily);
				break;

			case Repeating.TYPE_WEEKLY:
				retString = resources.getText(R.string.repeating_abbrev_weekly);
				break;

			case Repeating.TYPE_MONTHLY:
				retString = resources.getText(R.string.repeating_abbrev_monthly);
				break;

			case Repeating.TYPE_MONTHLY_DAY:
				retString = resources.getText(R.string.repeating_abbrev_monthly_day);
				break;

			case Repeating.TYPE_QUARTER_YEARLY:
				retString = resources.getText(R.string.repeating_abbrev_quarter_yearly);
				break;

			case Repeating.TYPE_YEARLY:
				retString = resources.getText(R.string.repeating_abbrev_yearly);
		}

		return retString;
	}

	@Override
	public IStorable fillFromCursor(Cursor cursor) {
		if (cursor == null) {
			setId(-1);
			return this;
		}

		if (!cursor.isAfterLast()) {
			// order is defined in getColumns
			setId(cursor.getLong(0));
			setActualDateLong(cursor.getLong(1));
			setEndDateLong(cursor.getLong(2));
			mType = cursor.getInt(3);
		}

		return this;
	}

	@Override
	public String[] getColumns() {
		return new String[] { DbAdapter.ID, DbAdapter.REPEATING_ACTUAL_DATE, DbAdapter.REPEATING_END_DATE, DbAdapter.REPEATING_TYPE };
	}

	@Override
	public ContentValues getContent() {
		final ContentValues content = new ContentValues();
		content.put(DbAdapter.REPEATING_ACTUAL_DATE, getActualDateLong());
		content.put(DbAdapter.REPEATING_END_DATE, getEndDateLong());
		content.put(DbAdapter.REPEATING_TYPE, mType);

		return content;
	}

	@Override
	public String getName() {
		return "";
	}

	@Override
	public String getTableName() {
		return DbAdapter.REPEATING_TABLE;
	}

	public boolean isEndDateNotSet() {
		return mEndDate.getTimeInMillis() == 0;
	}

	public void deleteAllPayments(DbAdapter dbHelper) {
		if (getId() > 0) {
			dbHelper.deleteEntries(new Payment(), DbAdapter.PAYMENT_REPEATING + "=" + getId());
		}
	}

	public void copyRepeating(Repeating sourceRepeating) {
		mActualDate = sourceRepeating.getActualDate();
		mEndDate = sourceRepeating.getEndDate();
		mOccurrences = sourceRepeating.getOccurrences();
		mType = sourceRepeating.getType();
	}

	/**
	 * Update existing repeating payments sourcePayment as start date
	 */
	public boolean updatePayments(Payment sourcePayment, DbAdapter dbHelper) {
		boolean result = true;
		Calendar compareDate = Calendar.getInstance();
		if (mEndDate.before(compareDate)) {
			compareDate = mEndDate;
		}

		// set first date from source payment
		mActualDate = (Calendar) sourcePayment.getDate().clone();

		final String where = DbAdapter.PAYMENT_REPEATING + "=" + getId();
		final Cursor cursor = dbHelper.fetchAllEntries(sourcePayment, where, DbAdapter.PAYMENT_DATE);

		if (!cursor.moveToFirst()) {
			return generateNewPayments(sourcePayment, dbHelper, null, null);
		}

		final Payment payment = new Payment();
		payment.copyPayment(sourcePayment);
		do {
			// rewrites payments in database
			payment.setId(cursor.getLong(0));
			if (mActualDate.after(compareDate)) {
				Log.d(TAG, "updatePayments - delete payment " + payment.getDateString());
				// delete unnecessarily payments
				dbHelper.deleteEntry(payment);
			} else {
				payment.setDate(mActualDate);
				Log.d(TAG, "updatePayments - update entry " + payment.getDateString());
				result = result && dbHelper.updateEntry(payment);
				changeActualDate();
			}
		} while (cursor.moveToNext());

		return result && generateNewPayments(payment, dbHelper, null, null);
	}

	/**
	 * generates new repeating payments
	 */
	public boolean generateNewPayments(Payment sourcePayment, DbAdapter dbHelper, Calendar returnDate, List<Long> accounts) {
		boolean result = true;

		final Payment payment = new Payment();

		if (sourcePayment == null) {
			// get payment of repeating
			sourcePayment = new Payment();
			final Cursor cursor = dbHelper.fetchAllEntries(sourcePayment, DbAdapter.PAYMENT_REPEATING + "=" + getId(), DbAdapter.PAYMENT_DATE + " DESC", 1);
			if (!cursor.moveToFirst()) {
				cursor.close();
				// repeating doesn't have payments
				dbHelper.deleteEntry(this);
				return false;
			}
			payment.fillFromCursor(cursor);
			Log.d(TAG, "generateNewPayments - payment from cursor " + payment.getDateString());
			cursor.close();
		} else {
			payment.copyPayment(sourcePayment);
			Log.d(TAG, "generateNewPayments - payment from source " + payment.getDateString());
		}

		// set end date, today or end of repeating
		Calendar compareDate = Calendar.getInstance();
		if (mEndDate.before(compareDate)) {
			compareDate = mEndDate;
		}

		// actual time by payment
		if (payment.getDateLong() > mActualDate.getTimeInMillis()) {
			mActualDate = (Calendar) payment.getDate().clone();
			changeActualDate();
		}

		// set return values for updating remainder
		if (returnDate != null) {
			returnDate.setTimeInMillis(mActualDate.getTimeInMillis());
			final long accountId = payment.getAccount().getId();
			if (!accounts.contains(accountId)) {
				accounts.add(accountId);
			}
			Log.d(TAG, "generateNewPayments - accounts " + accounts);
		}

		// generate payment for repeating
		while (mActualDate.before(compareDate) || mActualDate.equals(compareDate)) {
			payment.setDate(mActualDate);
			Log.d(TAG, "generateNewPayments - create payment " + payment.getDateString());
			result = result && dbHelper.createEntry(payment) > 0;
			changeActualDate();
		}
		return result;
	}

	private void changeActualDate() {
		switch (mType) {
			case Repeating.TYPE_NONE:
				break;

			case Repeating.TYPE_DAILY:
				mActualDate.add(Calendar.DATE, 1);
				break;

			case Repeating.TYPE_WEEKLY:
				mActualDate.add(Calendar.WEEK_OF_YEAR, 1);
				break;

			case Repeating.TYPE_MONTHLY:
				mActualDate.add(Calendar.MONTH, 1);
				break;

			case Repeating.TYPE_MONTHLY_DAY:
				final int week = mActualDate.get(Calendar.DAY_OF_WEEK_IN_MONTH);
				final int day = mActualDate.get(Calendar.DAY_OF_WEEK);
				mActualDate.add(Calendar.MONTH, 1);
				mActualDate.set(Calendar.DAY_OF_WEEK_IN_MONTH, week);
				mActualDate.set(Calendar.DAY_OF_WEEK, day);
				break;

			case Repeating.TYPE_QUARTER_YEARLY:
				mActualDate.add(Calendar.MONTH, 3);
				break;

			case Repeating.TYPE_YEARLY:
				mActualDate.add(Calendar.YEAR, 1);
		}
	}

	/**
	 * generate payments for repeating
	 */
	public static void generateAllRepeatedPayments(DbAdapter dbHelper) {
		// get all unfinished repeating
		final Cursor cursor = dbHelper.fetchAllEntries(new Repeating(), DbAdapter.REPEATING_ACTUAL_DATE + "<" + DbAdapter.REPEATING_END_DATE, DbAdapter.ID);
		if (!cursor.moveToFirst()) {
			cursor.close();
			return;
		}

		final Repeating repeating = new Repeating();
		final Calendar date = Calendar.getInstance();
		long firstDate = Calendar.getInstance().getTimeInMillis();
		final List<Long> accounts = new ArrayList<Long>(4);
		// generate payments for repeating
		do {
			repeating.fillFromCursor(cursor);
			repeating.generateNewPayments(null, dbHelper, date, accounts);
			Log.d(TAG, "generateAllRepeatedPayments " + repeating.getEndDateString());
			dbHelper.updateEntry(repeating);
			if (date.getTimeInMillis() < firstDate) {
				firstDate = date.getTimeInMillis();
			}
		} while (cursor.moveToNext());
		cursor.close();

		// update remainders in repeating interval
		final int count = accounts.size();
		for (int i = 0; i < count; i++) {
			PaymentList.updateIntervalRemainder(firstDate, Calendar.getInstance().getTimeInMillis(), accounts.get(i), dbHelper);
		}
	}

	public Repeating fillFromBundle(Bundle bundle) {
		if (bundle == null) {
			setId(-1);
			return this;
		}

		setId(bundle.getLong(REPEATING_ID));
		mType = bundle.getInt(DbAdapter.REPEATING_TYPE);
		setActualDateLong(bundle.getLong(DbAdapter.REPEATING_ACTUAL_DATE));
		setEndDateLong(bundle.getLong(DbAdapter.REPEATING_END_DATE));

		return this;
	}

	public Bundle fillBundle(Bundle bundle) {
		if (bundle == null) {
			return null;
		}

		bundle.putLong(REPEATING_ID, getId());
		bundle.putInt(DbAdapter.REPEATING_TYPE, mType);
		bundle.putLong(DbAdapter.REPEATING_ACTUAL_DATE, getActualDateLong());
		bundle.putLong(DbAdapter.REPEATING_END_DATE, getEndDateLong());

		return bundle;
	}

	public static void deleteUnused(DbAdapter dbHelper) {
		final String where = DbAdapter.ID + " not in (select distinct " + DbAdapter.PAYMENT_REPEATING + " from " + DbAdapter.PAYMENT_TABLE + " where "
		        + DbAdapter.PAYMENT_TYPE + "=" + Payment.TYPE_REMAINDER + ")";
		dbHelper.deleteEntries(new Repeating(), where);

	}
}
